﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class JengaEmitter : MonoBehaviour
{
    public LayerMask mask;
    public float forceImpluse = 5;
    public float force = 50;
    public GameObject jengaPrefab;
    public Material[] jengaMaterials;
    public int width;
    public int height;
    public float spaceX = 0.05f;
    public float spaceY = 0.015f;
    public float interval = 0.15f;
    private Rigidbody[] rbs;
    public float additionGravityFactor;
    public float rotYperLayer = 0;
    private void Reset()
    {
        mask = 1 << LayerMask.NameToLayer("Jenga");
    }
    void Start()
    {
        rbs = new Rigidbody[0];
        yieldI = new List<int>();
        StartCoroutine(EmitJenga());
    }
    private IEnumerator EmitJenga()
    {
        processing = true;
        for (int h = 0; h < height; h++)
        {
            yield return new WaitForSeconds(interval);
            for (int w = 0; w < width; w++)
            {
                var jenga = Instantiate(jengaPrefab).transform;
                jenga.name = "Jenga h " + h.ToString() + "w " + w.ToString();
                //jenga.GetComponent<Rigidbody>().isKinematic = true;
                var y = (jenga.localScale.y + spaceY) * h;
                var x = (jenga.localScale.x + spaceX) * w;
                var half = (jenga.localScale.x + spaceX) * (width - 1) * 0.5f;
                var angle = h * rotYperLayer;
                var rot = Quaternion.AngleAxis(angle, Vector3.up);
                if ((h & 1) == 0)
                {
                    jenga.GetComponent<MeshRenderer>().material = jengaMaterials[0];
                    jenga.SetParent(transform);
                    jenga.localEulerAngles = Vector3.up * angle;
                    jenga.localPosition = rot * new Vector3(x - half, y, 0);
                }
                else
                {
                    jenga.GetComponent<MeshRenderer>().material = jengaMaterials[1];
                    jenga.SetParent(transform);
                    jenga.localEulerAngles = Vector3.up * (90 + angle);
                    jenga.localPosition = rot * new Vector3(0, y, x - half);
                }
            }
        }
        rbs = GetComponentsInChildren<Rigidbody>();
        foreach (var rb in rbs)
        {
            rb.isKinematic = false;
        }
        yield return new WaitForSeconds(interval);
        processing = false;
    }
    private void FixedUpdate()
    {
        foreach (var rb in rbs)
        {
            rb.AddForce(Physics.gravity * additionGravityFactor);
        }
        UpdateForce();
    }
    public Rigidbody selectedObject;
    public Vector3 hitPrev;
    public Vector3 dirPrev;
    public bool processing;
    public float distance = 0.1f;
    public float speed = 0.01f;
    public float _speed;
    public float speedAcc = 0.001f;
    public float yOffset1 = 0.1f;
    public float yOffset2 = 0.1f;
    public List<int> yieldI;
    float GetHighest()
    {
        float highest = 0;
        foreach (var rb in rbs)
        {
            highest = Mathf.Max(highest, rb.position.y + rb.transform.localScale.y * 0.5f);
        }
        return highest;
    }
    IEnumerator BringUpJenga(Rigidbody rb, int yieldIndex)
    {
        yieldI.Add(0);
        yieldI[yieldIndex]++;
        Debug.Log("yield: " + yieldI[yieldIndex].ToString());
        rb.isKinematic = true;
        rb.GetComponent<Collider>().enabled = false;
        var highest = GetHighest();

        var targetPosition1 = Vector3.up * (highest + yOffset1);
        var targetPosition2 = Vector3.up * (highest + yOffset2);
        var targetPosition0 = rb.position;
        targetPosition0.y = highest + 1;
        Debug.Log("1");
        _speed = speed;
        while (Vector3.Distance(rb.position, targetPosition0) > distance)
        {
            var pos = Vector3.Lerp(rb.position, targetPosition0, _speed);
            _speed += speedAcc;
            rb.transform.position = pos;
            Debug.Log("1 while");
            yield return new WaitForFixedUpdate();
        }
        Debug.Log("2");
        rb.transform.localRotation = Quaternion.identity;
        _speed = speed;
        while (Vector3.Distance(rb.position, targetPosition1) > distance)
        {
            var pos = Vector3.Lerp(rb.position, targetPosition1, _speed);
            _speed += speedAcc;
            rb.transform.position = pos;
            Debug.Log("2 while");
            yield return new WaitForFixedUpdate();
        }
        Debug.Log("3");
        _speed = speed;
        while (Vector3.Distance(rb.position, targetPosition2) > distance)
        {
            var pos = Vector3.Lerp(rb.position, targetPosition2, _speed);
            _speed += speedAcc;
            rb.transform.position = pos;
            Debug.Log("3 while");
            yield return new WaitForFixedUpdate();
        }

        rb.isKinematic = false;
        rb.GetComponent<Collider>().enabled = true;
        yield return new WaitForSeconds(interval * 3);
        processing = false;
    }
    void UpdateForce()
    {
        if (processing) return;
        int sign = 0;
        if (Input.GetMouseButtonDown(0)) sign = -2;
        else if (Input.GetMouseButton(0)) sign = -1;
        else if (Input.GetMouseButton(1)) sign = 1;
        else if (Input.GetMouseButtonDown(1)) sign = 2;
        else
        {
            foreach (var rb in rbs)
            {
                if (rb.GetComponent<ColliderEvents>().touchRigidBodys.Count == 0)
                {
                    processing = true;
                    StartCoroutine(BringUpJenga(rb, yieldI.Count));
                }
                selectedObject = null;
            }
        }
        if (sign != 0)
        {
            if (selectedObject != null)
            {
                var hitPoint = selectedObject.transform.TransformPoint(hitPrev);
                if (Mathf.Abs(sign) > 1)
                {
                    selectedObject.AddForceAtPosition(dirPrev * forceImpluse * sign * 0.5f,
                        hitPoint, ForceMode.Impulse);
                }
                else
                {
                    selectedObject.AddForceAtPosition(dirPrev * force * sign,
                        hitPoint, ForceMode.Force);
                }
                return;
            }
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, Mathf.Infinity, mask.value))
            {
                var rigidBody = hit.transform.GetComponent<Rigidbody>();
                selectedObject = rigidBody;
                hitPrev = selectedObject.transform.InverseTransformPoint(hitPrev);
                dirPrev = (selectedObject.position - hit.point).normalized;
                if (Mathf.Abs(sign) > 1)
                {
                    selectedObject.AddForceAtPosition(dirPrev * forceImpluse * sign * 0.5f,
                        hit.point, ForceMode.Impulse);
                }
                else
                {
                    selectedObject.AddForceAtPosition(dirPrev.normalized * force * sign,
                        hit.point, ForceMode.Force);
                }
            }
        }
    }
}
